#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""Predesys Server - Core - Users management 
@version: 0.2.5
@license: GPL 3.0 (see "license" file)
@author: Jose A. Jimenez
@contact: jajimc@gmail.com"""

import sys
sys.dont_write_bytecode = True
import os
import os.path
DIR = os.path.abspath(os.path.dirname(os.path.realpath(__file__)))
sys.path.append(os.path.join(DIR, "../errorlog"))
import errorlog
import os
import random
import security
import coreexceptions
import sqlalchemy
import data
    
class UserGroup():        
    def __init__(self, id, name):
        self.set_id(id)
        self.set_name(name)
    
    def get_id(self):
        return self._id
            
    def get_name(self):
        return self._name
    
    def set_id(self, id):
        if id == None or (type(id) != str and type(id) != unicode) or id == "": raise coreexceptions.ValueNotValidException("'id' must be a not null and not empty string")
        self._id = id
            
    def set_name(self, name):
        if name != None and (type(name) != str and type(name) != unicode): raise coreexceptions.ValueNotValidException("'name' must a string or null")
        
        if name == "": self.name = None
        else: self._name = name
        
class User():
    def __init__(self, id, group, enabled, name, email, macAddresses):
        self.set_id(id)
        self.set_group(group)
        self.enable(enabled)
        self.set_name(name)
        self.set_email(email)
        self.set_mac_addresses(macAddresses)
        
    def get_id(self):
        return self._id
        
    def get_group(self):
        return self._group
        
    def is_enabled(self):
        return self._enabled
        
    def get_name(self):
        return self._name
        
    def get_email(self):
        return self._email
    
    def get_mac_addresses(self):
        return self._macAddresses
    
    def set_id(self, id):
        if id == None or (type(id) != str and type(id) != unicode) or id == "": raise coreexceptions.ValueNotValidException("'id' must be a not null and not empty string")
        self._id = id
    
    def set_group(self, group):
        if group != None and not isinstance(group, UserGroup): raise coreexceptions.ValueNotValidException("'group' must be an UserGroup object or null")
        self._group = group
        
    def enable(self, enabled):
        if enabled == None or type(enabled) != bool: raise coreexceptions.ValueNotValidException("'enabled' must be a not null boolean")
        self._enabled = enabled
        
    def set_name(self, name):
        if name != None and (type(name) != str and type(name) != unicode): raise coreexceptions.ValueNotValidException("'name' must be a string or null")
        
        if name == "": self.name = None
        self._name = name
        
    def set_email(self, email):
        if email != None and (type(email) != str and type(email) != unicode): raise coreexceptions.ValueNotValidException("'email' must be a string or null")
        
        if email == "": self.email = None
        self._email = email
        
    def set_mac_addresses(self, macAddresses):
        if macAddresses != None and type(macAddresses) != list: raise coreexceptions.ValueNotValidException("'macAddresses' must be a string list or null")
        
        if macAddresses == None: macAddresses = []
        self._macAddresses = macAddresses
        
class MacAddress():
    def __init__(self, address, id):
        self.set_address(address)
        self.set_user_id(id)
    
    def get_address(self):
        return self._address
            
    def get_user_id(self):
        return self._user_id
    
    def set_address(self, address):
        if address == None or (type(address) != str and type(address) != unicode) or address == "": raise coreexceptions.ValueNotValidException("'address' must be a not null and not empty string")
        self._address = address
            
    def set_user_id(self, id):
        if id == None or (type(id) != str and type(id) != unicode) or id == "": raise coreexceptions.ValueNotValidException("'id' must be a not null and not empty string")
        self._id = id
    
def get_user_groups():
    """Gets all user groups.
    @return: List of user groups
    @rtype: C{list}"""
    groups = []
    
    try:
        dbConnection = data.open_connection()
        statement = "select id from user_groups;"
        result = data.execute(dbConnection, statement)
        for row in result:
            groups.append(row["id"])
        result.close()
        data.close_connection(dbConnection)
    except Exception as exc:
        errorlog.log_exception(exc.args[0], "predesys-server.core.users.get_user_groups")
        raise
        
    return groups

def is_user_group(id):
    """Gets if a string corresponds to a registered user group.
    @param id: User group ID
    @type id: C{str}
    @return: *True* if *id* corresponds to a registered user group. *False* in other case.
    @rtype: C{bool}"""
    if id == None or (type(id) != str and type(id) != unicode) or id == "" : raise coreexceptions.ValueNotValidException("'id' must be a valid string")

    try:
        dbConnection = data.open_connection()
        statement = "select id from user_groups where id='%s';" % id
        result = data.execute(dbConnection, statement)
        row = result.fetchone()
        result.close()
        data.close_connection(dbConnection)
    except Exception as exc:
        errorlog.log_exception(exc.args[0], "predesys-server.core.users.is_user_group")
        raise
    
    return (row != None)
    
def get_user_group_data(id):
    """Gets a user group data.
    @param id: User group ID
    @type id: C{str}
    @return: User group data
    @rtype: L{UserGroup}"""
    if id == None or (type(id) != str and type(id) != unicode) or id == "": raise coreexceptions.ValueNotValidException("'id' must be a not null and not empty string")
    if not is_user_group(id): raise coreexceptions.SystemException("'%s' is not a user group" % id)
    
    userGroupData = None
    
    try:
        dbConnection = data.open_connection()
        statement = "select * from user_groups where id='%s';" % id
        result = data.execute(dbConnection, statement)
        row = result.fetchone()
        userGroupData = UserGroup(row["id"], row["name"])
        result.close()
        data.close_connection(dbConnection)
    except Exception as exc:
        errorlog.log_exception(exc.args[0], "predesys-server.core.users.get_user_group_data")
        raise
        
    return userGroupData
    
def add_user_group(groupData):
    """Adds a new user group.
    @param groupData: User group to add
    @type groupData: L{UserGroup}"""
    security.check_root()
    if groupData == None or not isinstance(groupData, UserGroup): raise coreexceptions.ValueNotValidException("'groupData' must be a not null UserGroup object")
    id = groupData.get_id()
    if is_user_group(id): raise coreexceptions.SystemException("'%s' already exists" % id)
    
    name = groupData.get_name()
    if (name == None or name == ""): name = "NULL"
    else: name = "'%s'" % name
    
    try:
        dbConnection = data.open_connection()
        statement = "insert into user_groups values ('%s', %s);" % (id, name)
        result = data.execute(dbConnection, statement)
        result.close()
        data.close_connection(dbConnection)
    except Exception as exc:
        errorlog.log_exception(exc.args[0], "predesys-server.core.users.add_user_group")
        raise

def modify_user_group(oldId, newId, name):
    """Modifies an existing user group.
    @param oldId: User group current ID
    @type oldId: C{str}
    @param newId: User group new ID
    @type newId: C{str}
    @param name: User group new name
    @type name: C{str}"""
    security.check_root()
    if oldId == None or type(oldId) != str or oldId == "" : raise coreexceptions.ValueNotValidException("'oldId' must be a not null and not empty string")
    if newId == None or type(newId) != str or newId == "" : raise coreexceptions.ValueNotValidException("'newId' must be a not null and not empty string")
    if name != None and type(name) != str: raise coreexceptions.ValueNotValidException("'name' must be a valid string")
    
    if not is_user_group(oldId): raise coreexceptions.SystemException("'%s' is not a user group" % oldId)
    if oldId != newId and is_user_group(newId): raise coreexceptions.SystemException("'%s' already exists" % newId)
    
    if oldId == "admin" or oldId == "client" or oldId == "station" or newId == "admin" or newId == "client" or newId == "station":
        raise coreexceptions.ValueNotValidException("'%s' is a special user group, it can not be modified or deleted" % oldId)
    
    if (name == None or name == ""): name = "NULL"
    else: name = "'%s'" % name
    
    try:
        dbConnection = data.open_connection()
        statement = "update user_groups set id='%s', name=%s where id='%s';" % (newId, name, oldId)
        result = data.execute(dbConnection, statement)
        result.close()
        data.close_connection(dbConnection)
    except Exception as exc:
        errorlog.log_exception(exc.args[0], "predesys-server.core.users.modify_user_group")
        raise

def delete_user_group(id):
    """Deletes an existing user group.
    @param id: User group ID
    @type id: C{str}"""
    security.check_root()
    if id == None or (type(id) != str and type(id) != unicode) or id == "": raise coreexceptions.ValueNotValidException("'id' must be a not null and not empty string")
    if not is_user_group(id): raise coreexceptions.SystemException("'%s' is not a user" % id)   
    if id == "admin" or id == "client" or id == "station":
        raise coreexceptions.ValueNotValidException("'%s' is a special user group, it can not be modified or deleted" % id)
        
    try:        
        dbConnection = data.open_connection()
        statement = "delete from user_groups where id='%s';" % id
        result = data.execute(dbConnection, statement)
        result.close()
        data.close_connection(dbConnection)
    except Exception as exc:
        errorlog.log_exception(exc.args[0], "predesys-server.core.users.delete_user_group")
        raise
        
def get_users():
    """Gets all users.
    @return: List of users
    @rtype: C{list}"""
    users = []
    
    try:
        dbConnection = data.open_connection()
        statement = "select id from users;"
        result = data.execute(dbConnection, statement)   
        for row in result:
            users.append(row["id"])
        result.close()
        data.close_connection(dbConnection)
    except Exception as exc:
        errorlog.log_exception(exc.args[0], "predesys-server.core.users.get_users")
        raise
    
    return users

def is_user(id):
    """Gets if a string corresponds to a registered user.
    @param id: User ID
    @type id: C{str}
    @return: *True* if *id* corresponds to a registered user. *False* in other case.
    @rtype: C{bool}"""
    if id == None or (type(id) != str and type(id) != unicode) or id == "": raise coreexceptions.ValueNotValidException("'id' must be a not null and not empty string")

    try:
        dbConnection = data.open_connection()
        statement = "select * from users where id='%s';" % id
        result = data.execute(dbConnection, statement)
        row = result.fetchone()
        result.close()
        data.close_connection(dbConnection)
    except Exception as exc:
        errorlog.log_exception(exc.args[0], "predesys-server.core.users.is_user")
        raise
    
    return (row != None)
    
def get_user_data(id):
    """Gets a user data.
    @param id: User ID
    @type id: C{str}
    @return: User data
    @rtype: L{User}"""
    if id == None or (type(id) != str and type(id) != unicode) or id == "": raise coreexceptions.ValueNotValidException("'id' must be a not null and not empty string")
    if not is_user(id): raise coreexceptions.SystemException("'%s' is not a user" % id)
    
    userData = None
    
    try:
        dbConnection = data.open_connection()
        statement = "select * from users where id='%s';" % id
        result = data.execute(dbConnection, statement)
        row = result.fetchone()   
        if row != None:
            addresses = []
            statement2 = "select address from addresses where user_id='%s';" % id
            result2 = data.execute(dbConnection, statement2)
            for row2 in result2:
                addresses.append(row2["address"])
            
            if row["user_group_id"] != None and row["user_group_id"] != "": group = UserGroup(row["user_group_id"], None)
            else: group = None
            
            userData = User(row["id"], group, bool(int(row["enabled"])), row["name"], row["email"], addresses)         
            result2.close()
        result.close()
        data.close_connection(dbConnection)
    except Exception as exc:
        errorlog.log_exception(exc.args[0], "predesys-server.core.users.get_user_data")
        raise
        
    return userData

def is_enabled_user(id):
    """Gets if a string corresponds to a enabled registered user.
    @param id: User ID
    @type id: C{str}
    @return: *True* if *id* corresponds to a enabled registered user. *False* in other case.
    @rtype: C{bool}"""
    if id == None or (type(id) != str and type(id) != unicode) or id == "": raise coreexceptions.ValueNotValidException("'id' must be a not null and not empty string")
    if not is_user(id): raise coreexceptions.SystemException("'%s' is not a user" % id)
    
    try:
        dbConnection = data.open_connection()
        statement = "select * from users where id='%s';" % id
        result = data.execute(dbConnection, statement)
        row = result.fetchone()
        result.close()
        data.close_connection(dbConnection)
    except Exception as exc:
        errorlog.log_exception(exc.args[0], "predesys-server.core.users.is_enabled_user")
        raise
    
    return (row != None and bool(int(row["enabled"])))
    
def add_user_no_password(userData):
    """Adds a new user with a random password.
    @param userData: User to add
    @type userData: L{User}"""
    security.check_root()
    
    try:
        add_user(userData, get_random_password(8))
    except Exception as exc:
        errorlog.log_exception(exc.args[0], "predesys-server.core.users.add_user_no_password")
        raise
        
def add_user(userData, userPassword):
    """Adds a new user.
    @param userData: User to add
    @type userData: L{User}
    @param userPassword: User password
    @type userPassword: C{str}"""
    security.check_root()
    if userData == None or not isinstance(userData, User): raise coreexceptions.ValueNotValidException("'userData' must be a not null User object")
    if userPassword == None or type(userPassword) != str or userPassword == "": raise coreexceptions.ValueNotValidException("'userPassword' must be a not null and not empty string")
    id = userData.get_id()
    if is_user(id): raise coreexceptions.SystemException("'%s' already exists" % id)
    
    group = userData.get_group()
    if (group == None):
        groupId = "NULL"
    else:
        groupId = group.get_id()
        if not is_user_group(groupId): raise coreexceptions.SystemException("'%s' is not a user group" % groupId) 
        groupId = "'%s'" % groupId
        
    if userData.is_enabled(): enabled = 1
    else: enabled = 0
        
    name = userData.get_name()
    if (name == None or name == ""): name = "NULL"
    else: name = "'%s'" % name
    
    email = userData.get_email()
    if (email == None or email == ""): email = "NULL"
    else: email = "'%s'" % email
    
    try:
        dbConnection = data.open_connection()
        statement = "insert into users values ('%s', %s, %d, '%s', %s, %s);" % (id, groupId, enabled, security.get_md5_sum(userPassword), name, email)
        result = data.execute(dbConnection, statement)
        result.close()
        data.close_connection(dbConnection)
        for address in userData.get_mac_addresses():
            add_address_user_pair(address, id)
    except Exception as exc:
        errorlog.log_exception(exc.args[0], "predesys-server.core.users.add_user")   
        raise
    
def modify_user(oldId, newId, groupId, name, email, macAddresses):
    """Modifies an existing user.
    @param oldId: User current ID
    @type oldId: C{str}
    @param newId: User new ID
    @type newId: C{str}
    @param name: User new name
    @type name: C{str}
    @param email: User new e-mail address
    @type email: C{str}
    @param macAddresses: User new MAC addresses
    @type macAddresses: C{list}"""
    security.check_root()
    if oldId == None or type(oldId) != str or oldId == "" : raise coreexceptions.ValueNotValidException("'oldId' must be a not null and not empty string")
    if newId == None or type(newId) != str or newId == "" : raise coreexceptions.ValueNotValidException("'newId' must be a not null and not empty string")
    if groupId != None and (type(groupId) != str or groupId == ""): raise coreexceptions.ValueNotValidException("'groupId' must be a not empty string or null")
    if name != None and type(name) != str: raise coreexceptions.ValueNotValidException("'name' must be a valid string")
    if email != None and type(email) != str: raise coreexceptions.ValueNotValidException("'email' must be a valid string")
    if macAddresses != None and type(macAddresses) != list: raise coreexceptions.ValueNotValidException("'macAddresses' must be a string list or null")
    if oldId == "admin": raise coreexceptions.ValueNotValidException("'%s' is a special user, it can not be modified or deleted" % oldId)
    
    if macAddresses == None: macAddresses = []
    
    if not is_user(oldId): raise coreexceptions.SystemException("'%s' is not a user" % oldId)
    if oldId != newId and is_user(newId): raise coreexceptions.SystemException("'%s' already exists" % newId)
    
    if groupId != None and not is_user_group(groupId): raise coreexceptions.SystemException("'%s' is not a user group" % groupId)
    if groupId == None or groupId == "":
        groupId = "NULL"
    else:
        groupId = "'%s'" % groupId
    
    if (name == None or name == ""): name = "NULL"
    else: name = "'%s'" % name
    
    if (email == None or email == ""): email = "NULL"
    else: email = "'%s'" % email
    
    try:
        delete_user_addresses(oldId)
        
        dbConnection = data.open_connection()
        statement = "update users set id='%s', user_group_id=%s, name=%s, email=%s where id='%s';" % (newId, groupId, name, email, oldId)
        result = data.execute(dbConnection, statement)
        result.close()
        data.close_connection(dbConnection)
        
        for address in macAddresses:
            add_address_user_pair(address, newId)
    except Exception as exc:
        errorlog.log_exception(exc.args[0], "predesys-server.core.users.modify_user")
        raise
 
def enable_user(id, enabled):
    """Enables/disables an existing user.
    @param id: User ID
    @type id: C{str}
    @param enabled: If *True*, user will be enabled; disabled in other case.
    @type enabled: C{bool}"""
    security.check_root()
    if id == None or (type(id) != str and type(id) != unicode) or id == "": raise coreexceptions.ValueNotValidException("'id' must be a not null and not empty string")
    if enabled == None or type(enabled) != bool: raise coreexceptions.ValueNotValidException("'enabled' must be a not null boolean")
    if not is_user(id): raise coreexceptions.SystemException("'%s' is not a user" % id)
    
    if enabled: enabled = 1
    else: enabled = 0
    
    try:
        dbConnection = data.open_connection()
        statement = "update users set enabled=%s where id='%s';" % (enabled, id)
        result = data.execute(dbConnection, statement);
        result.close()
        data.close_connection(dbConnection)
    except Exception as exc:
        errorlog.log_exception(exc.args[0], "predesys-server.core.users.enable_user")
        raise
           
def set_user_password(id, password):
    """Sets the password of an existing user.
    @param id: User ID
    @type id: C{str}
    @param password: User new password
    @type password: C{str}"""
    security.check_root()
    if id == None or (type(id) != str and type(id) != unicode) or id == "": raise coreexceptions.ValueNotValidException("'id' must be a not null and not empty string")
    if password == None or (type(password) != str and type(password) != unicode) or password == "": raise coreexceptions.ValueNotValidException("'password' must be a valid string")
    if not is_user(id): raise coreexceptions.SystemException("'%s' is not a user" % oldId)
    
    try:
        dbConnection = data.open_connection()
        statement = "update users set passwd='%s' where id='%s';" % (security.get_md5_sum(password), id)
        result = data.execute(dbConnection, statement);
        result.close()
        data.close_connection(dbConnection)
    except Exception as exc:
        errorlog.log_exception(exc.args[0], "predesys-server.core.users.set_user_password")
        raise
        
def delete_user(id):
    """Deletes an existing user.
    @param id: User ID
    @type id: C{str}"""
    security.check_root()
    if id == None or (type(id) != str and type(id) != unicode) or id == "": raise coreexceptions.ValueNotValidException("'id' must be a valid user ID")
    if not is_user(id): raise coreexceptions.SystemException("'%s' is not a user" % id)
    if id == "admin": raise coreexceptions.ValueNotValidException("'%s' is a special user, it can not be modified or deleted" % id)
    
    try:
        delete_user_addresses(id)
        
        dbConnection = data.open_connection()
        statement = "delete from users where id='%s';" % id
        result = data.execute(dbConnection, statement)
        result.close()
        data.close_connection(dbConnection)
    except Exception as exc:
        errorlog.log_exception(exc.args[0], "predesys-server.core.users.delete_user")
        raise
        
def get_address_user_pairs():
    """Gets every address-user pairs.
    @return: A list of all address-user pairs
    @rtype: C{list}"""
    pairs = []
    
    try:
        dbConnection = data.open_connection()
        statement = "select * from addresses;"
        result = data.execute(dbConnection, statement)
        for row in result:
            pairs.append([row["address"], row["user_id"]])
        result.close()
        data.close_connection(dbConnection)
    except Exception as exc:
        errorlog.log_exception(exc.args[0], "predesys-server.core.users.get_address_user_pairs")
        raise
    
    return pairs

def is_address_user_pair(address, userId):
    """Gets if an address-user pair is a registered pair.
    @param address: User MAC address
    @type address: C{str}
    @param userId: User ID
    @type userId: C{str}
    @return: *True* if pair is a registered pair, *False* in other case.
    @rtype: C{bool}"""
    if address == None or (type(address) != str and type(address) != unicode) or address == "": raise coreexceptions.ValueNotValidException("'address' must be a valid string")
    if userId == None or (type(userId) != str and type(userId) != unicode) or userId == "": raise coreexceptions.ValueNotValidException("'userId' must be a valid user ID")
    
    try:
        dbConnection = data.open_connection()
        statement = "select address, user_id from addresses where address='%s' and user_id='%s';" % (address, userId)
        result = data.execute(dbConnection, statement)
        row = result.fetchone()
        result.close()
        data.close_connection(dbConnection)
    except Exception as exc:
        errorlog.log_exception(exc.args[0], "predesys-server.core.users.is_address_user_pair")
        raise
    
    return (row != None)
    
def get_addresses():
    """Gets all MAC addresses.
    @return: A list of all registered MAC addresses
    @rtype: C{list}"""
    addresses = []
    
    try:
        dbConnection = data.open_connection()
        statement = "select address from addresses;"
        result = data.execute(dbConnection, statement)
        for row in result:
            addresses.append(row["address"])
        result.close()
        data.close_connection(dbConnection)
    except Exception as exc:
        errorlog.log_exception(exc.args[0], "predesys-server.core.users.get_addresses")
        raise
    
    return addresses
    
def is_address(address):
    """Gets if an address is a registered address.
    @param address: MAC address
    @type address: C{str}
    @return: *True* if address is a registered address, *False* in other case.
    @rtype: C{bool}"""
    if address == None or (type(address) != str and type(address) != unicode) or address == "": raise coreexceptions.ValueNotValidException("'address' must be a valid string")

    try:
        dbConnection = data.open_connection()
        statement = "select address from addresses where address='%s';" % address
        result = data.execute(dbConnection, statement)
        row = result.fetchone()
        result.close()
        data.close_connection(dbConnection)
    except Exception as exc:
        errorlog.log_exception(exc.args[0], "predesys-server.core.is_address")
        raise
    
    return (row != None)
    
def get_address_users(address):
    """Gets all the users that have associated a concrete address.
    @param address: MAC address
    @type address: C{str}
    @return: A list of users that have associated the address
    @rtype: C{list}"""
    if address == None or (type(address) != str and type(address) != unicode) or address == "": raise coreexceptions.ValueNotValidException("'address' must be a valid user ID")
    if not is_address(address): raise coreexceptions.SystemException("'%s' is not an address" % address)
    
    users = []
    
    try:
        dbConnection = data.open_connection()
        statement = "select user_id from addresses where address='%s';" % address
        result = data.execute(dbConnection, statement)
        for row in result:
            users.append(row["user_id"])
        result.close()
        data.close_connection(dbConnection)
    except Exception as exc:
        errorlog.log_exception(exc.args[0], "predesys-server.core.get_address_users")
        raise
    
    return users
    
def add_address_user_pair(address, userId):
    """Adds a new address associating it with an existing user.
    @param address: New address to register
    @type address: C{str}
    @param userId: User ID
    @type userId: C{str}"""
    security.check_root()
    if address == None or (type(address) != str and type(address) != unicode) or address == "" : raise coreexceptions.ValueNotValidException("'address' must be a valid string")
    if userId == None or (type(userId) != str and type(userId) != unicode) or userId == "" : raise coreexceptions.ValueNotValidException("'userId' must be a valid user ID")
    if not is_user(userId): raise coreexceptions.SystemException("'%s' is not a user" % userId)
    if is_address_user_pair(address, userId): raise coreexceptions.SystemException("('%s', '%s') address-user pair already exists" % (address, userId))
    
    try:
        dbConnection = data.open_connection()
        statement = "insert into addresses values ('%s', '%s');" % (address, userId)
        result = data.execute(dbConnection, statement);
        result.close()
        data.close_connection(dbConnection)
    except Exception as exc:
        errorlog.log_exception(exc.args[0], "predesys-server.core.add_address_user_pair")
        raise
    
def modify_address_user_pair(oldAddress, oldUserId, newAddress, newUserId):
    """Modifies an existing address-user pair.
    @param oldAddress: Current pair address
    @type oldAddress: C{str}
    @param oldUserId: Current pair user ID
    @type oldUserId: C{str}
    @param newAddress: New pair address
    @type newAddress: C{str}
    @param newUserId: New pair user ID
    @type newUserId: C{str}"""
    security.check_root()
    if oldAddress == None or (type(oldAddress) != str and type(oldAddress) != unicode) or oldAddress == "": raise coreexceptions.ValueNotValidException("'oldAddress' must be a valid string")
    if oldUserId == None or (type(oldUserId) != str and type(oldUserId) != unicode) or oldUserId == "": raise coreexceptions.ValueNotValidException("'oldUserId' must be a valid user ID")
    if newAddress == None or (type(newAddress) != str and type(newAddress) != unicode) or newAddress == "": raise coreexceptions.ValueNotValidException("'newAddress' must be a valid string")
    if newUserId == None or (type(newUserId) != str and type(newUserId) != unicode) or newUserId == "": raise coreexceptions.ValueNotValidException("'newUserId' must be a valid user ID")   
    if not is_address_user_pair(oldAddress, oldUserId): raise coreexceptions.SystemException("('%s', '%s') is not an address-user pair" % (oldAddress, oldUserId))
    if oldAddress != newAddress and oldUserId != newUserId and is_address_user_pair(newAddress, newUserId):
        raise coreexceptions.SystemException("('%s', '%s') address-user pair already exists" % (newAddress, newUserId))
    if not is_user(newUserId): raise coreexceptions.SystemException("'%s' is not a user" % newUserId)
    
    try:
        dbConnection = data.open_connection()
        statement = "update addresses set address='%s', user_id='%s' where address='%s' and user_id='%s';" % (newAddress, newUserId, oldAddress, oldUserId)
        result = data.execute(dbConnection, statement);
        result.close()
        data.close_connection(dbConnection)
    except Exception as exc:
        errorlog.log_exception(exc.args[0], "predesys-server.core.modify_address_user_pair")
        raise

def delete_address_user_pair(address, userId):
    """Deletes an existing address-user pair. It only deletes the address.
    @param address: Pair address
    @type address: C{str}
    @param userId: Pair user ID
    @type userId: C{str}"""
    security.check_root()
    if address == None or (type(address) != str and type(address) != unicode) or address == "": raise coreexceptions.ValueNotValidException("'address' must be a valid string")
    if userId == None or (type(userId) != str and type(userId) != unicode) or userId == "": raise coreexceptions.ValueNotValidException("'userId' must be a valid user ID")
    if not is_address_user_pair(oldAddress, userId): raise coreexceptions.SystemException("('%s', '%s') is not an address-user pair" % (oldAddress, userId))
    
    try:
        dbConnection = data.open_connection()
        statement = "delete from addresses where address='%s' and user_id='%s';" % (address, userId)
        result = data.execute(dbConnection, statement)
        result.close()
        data.close_connection(dbConnection)
    except Exception as exc:
        errorlog.log_exception(exc.args[0], "predesys-server.core.delete_address_user_pair")
        raise
    
def delete_address(address):
    """Deletes an existing address. Every address-user pair is deleted (only the address).
    @param address: Address to delete
    @type address: C{str}"""
    security.check_root()
    if address == None or (type(address) != str and type(address) != unicode) or address == "" : raise coreexceptions.ValueNotValidException("'address' must be a valid string")
    if not is_address(address): raise coreexceptions.SystemException("'%s' is not an address" % address)
    
    try:
        dbConnection = data.open_connection()
        statement = "delete from addresses where address='%s';" % address
        result = data.execute(dbConnection, statement)
        result.close()
        data.close_connection(dbConnection)
    except Exception as exc:
        errorlog.log_exception(exc.args[0], "predesys-server.core.delete_address")
        raise
    
def delete_user_addresses(userId):
    """Deletes all the addresses (address-user pairs) associated with an existing user.
    @param userId: User ID
    @type userId: C{str}"""
    security.check_root()
    if userId == None or (type(userId) != str and type(userId) != unicode) or userId == "": raise coreexceptions.ValueNotValidException("'userId' must be a valid user ID")
    if not is_user(userId): raise coreexceptions.SystemException("'%s' is not a user" % userId)
    
    try:
        dbConnection = data.open_connection()
        statement = "delete from addresses where user_id='%s';" % userId
        result = data.execute(dbConnection, statement)
        result.close()
        data.close_connection(dbConnection)
    except Exception as exc:
        errorlog.log_exception(exc.args[0], "predesys-server.core.delete_user_addresses")
        raise
    
def get_random_password(length):
    """Gets a random alphanumeric password.
    @param length: Password length (in characters)
    @type length: C{int}"""
    if length == None or type(length) != int or length == 0: raise coreexceptions.ValueNotValidException("'length' must be an integer greater than 0")

    characters = "abcdefghijklmnopqrstuvwxyz"
    characters += characters.upper()
    characters += "0123456789"
    charactersLength = len(characters)
    password = ""
    
    for i in range(0, length):
        password += characters[random.randrange(0, charactersLength)]
        
    return password
